﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Threading;
using SimpleServiceBus.Endpoint;
using SimpleServiceBus.Endpoint.Management.Monitoring;
using SimpleServiceBus.Messages.Management.Monitoring;
using SimpleServiceBus.Utilities;

namespace SimpleServiceBus.EndpointManagement.Server
{
    public class EndpointStatusAggregationService : TimerServiceBase, IEndpointStatusAggregationService
    {
        private readonly ReaderWriterLock _aggLock = new ReaderWriterLock();

        private readonly Dictionary<string, Dictionary<string, EndpointMeasurementSummary>> _aggregations =
            new Dictionary<string, Dictionary<string, EndpointMeasurementSummary>>();

        private DateTime _aggregationStarted;
        public TimeSpan ReadWriteTimeout { get; set; }

        public event EventHandler<EndpointStatusSummaryEventArgs> EndpointStatusSummaryUpdated;
     

        #region IEndpointStatusAggregationService Members

        public IEndpointMonitorServerService EndpointMonitor { get; set; }

        public TimeSpan StatusBroadcastInterval
        {
            get { return Interval; }
            set { Interval = value; }
        }

        public IMessagingEndpoint Endpoint { get; set; }

        #endregion

        protected override void StartService()
        {
            PreventConcurrentIntervalProcessing = true;
            if (ReadWriteTimeout == TimeSpan.Zero) ReadWriteTimeout = TimeSpan.FromSeconds(30);

            EndpointMonitor = Endpoint.LocalServices.GetService<IEndpointMonitorServerService>();
            if (EndpointMonitor == null)
                throw new ConfigurationErrorsException(
                    "No endpoint monitor has been configured in the system. The EndpointStatusAggregationService requires a valid EndpointMonitor be in use at this endpoint.");

            EndpointMonitor.EndpointStatusExpired += EndpointMonitor_EndpointStatusExpired;
            EndpointMonitor.EndpointStatusUpdated += EndpointMonitor_EndpointStatusUpdated;

           
            base.StartService();
        }

        protected override void ShutDownService()
        {
            base.ShutDownService();
            EndpointMonitor.EndpointStatusExpired -= EndpointMonitor_EndpointStatusExpired;
            EndpointMonitor.EndpointStatusUpdated -= EndpointMonitor_EndpointStatusUpdated;
        }


        protected void ClearAggregationsForEndpoint(string endpointId)
        {
            using (new WriterLock(_aggLock, ReadWriteTimeout))
            {
                if (_aggregations.ContainsKey(endpointId))
                    _aggregations.Remove(endpointId);
            }
        }

        protected virtual void AggregateEndpointMeasurements(EndpointStatusUpdateNotification update)
        {
            if (update == null || update.EndpointMeasurements == null || update.EndpointMeasurements.Count < 1)
                return;

            if (_aggregationStarted == DateTime.MinValue)
                _aggregationStarted = DateTime.Now.ToUniversalTime();

            using (new WriterLock(_aggLock, ReadWriteTimeout))
            {
                Dictionary<string, EndpointMeasurementSummary> measurements;

                if (_aggregations.ContainsKey(update.EndpointID))
                    measurements = _aggregations[update.EndpointID];
                else
                {
                    measurements = new Dictionary<string, EndpointMeasurementSummary>();
                    _aggregations[update.EndpointID] = measurements;
                }

                foreach (EndpointMeasurement measurement in update.EndpointMeasurements)
                {
                    EndpointMeasurementSummary summary;
                    if (measurements.ContainsKey(measurement.Name))
                    {
                        summary = measurements[measurement.Name];
                    }
                    else
                    {
                        summary = new EndpointMeasurementSummary();
                        measurements.Add(measurement.Name, summary);
                    }

                    summary.AddMeasurement(measurement);
                }
            }
        }

        protected override void PerformTimerAction()
        {
            using (new BusyHandle(this))
            {
                BroadcastStatusUpdate();
            }
        }

        protected virtual void BroadcastStatusUpdate()
        {
            var endpoints = new List<EndpointStatusSummary>();

            using (new WriterLock(_aggLock, ReadWriteTimeout))
            {
                foreach (string endpointId in _aggregations.Keys)
                {
                    EndpointStatusSummary endpoint = GetEndpointStatusSummary(endpointId);
                    if (endpoint != null)
                        endpoints.Add(endpoint);
                }

                var notification = new EndpointStatusSummaryNotification
                                       {
                                           AggregationStartTime = _aggregationStarted,
                                           AggregationStopTime = DateTime.Now.ToUniversalTime(),
                                           ActiveEndpoints = endpoints
                                       };
                
                Endpoint.MessageBus.Publish(notification);
                OnEndpointStatusSummaryUpdated(notification);

                _aggregations.Clear();
            }
        }

        protected virtual void OnEndpointStatusSummaryUpdated(EndpointStatusSummaryNotification summary)
        {
            EventHandler<EndpointStatusSummaryEventArgs> evt = EndpointStatusSummaryUpdated;
            if (evt != null)
                evt(this, new EndpointStatusSummaryEventArgs(summary));
        }

        private void EndpointMonitor_EndpointStatusUpdated(object sender, EndpointStatusEventArgs e)
        {
            if (e.LastStatusMessage is EndpointStatusUpdateNotification)
            {
                using (new BusyHandle(this))
                {
                    AggregateEndpointMeasurements((EndpointStatusUpdateNotification) e.LastStatusMessage);
                }
            }
            else if (e.LastStatusMessage is EndpointOfflineNotification)
            {
                ClearAggregationsForEndpoint(e.EndpointID);
            }
        }

        private void EndpointMonitor_EndpointStatusExpired(object sender, EndpointStatusEventArgs e)
        {
            ClearAggregationsForEndpoint(e.EndpointID);
        }

        private EndpointStatusSummary GetEndpointStatusSummary(string endpointId)
        {
            Dictionary<string, EndpointMeasurementSummary> measurements = _aggregations[endpointId];
            if (measurements != null)
            {
                var measurementList = new List<EndpointMeasurementSummary>(measurements.Values);

                return new EndpointStatusSummary
                           {
                               EndpointID = endpointId,
                               StatusMeasurements = measurementList,
                               StatusTime = DateTime.Now.ToUniversalTime()
                           };
            }
            return null;
        }
    }
}